#include "kernel.h"
unsigned long pci_entry_point;

/*
 * 
 * PCI bios management functions
 *
 */


/*
 * 
 * Physical address of PCI bios service directory
 * 
 */
typedef struct bios32_indirect
	{
		dword addr;
		unsigned short segment;
	} __attribute__((packed)) bios32_indirect_t;

bios32_indirect_t ind;
bios32_indirect_t interface;

/*
 *
 * Detect presence of PCI bios service directory
 *
 * NOTE: If PCI bios service directory headers
 *  namely _32_ is present in the memory range 
 *  0xe0000 to 0xfffff, then PCI bios service directory is present
 *
 * NOTE: The entry point address is in the location after _32_ 
 *
 */
unsigned int det_pres_bios32_service_directory()
{
	unsigned int ret = 0;

	unsigned char *bios32_start = (unsigned char *)0xe0000, *bios32_end = (unsigned char *)0xfffff;

	while (bios32_start < bios32_end)
	{
		if(*bios32_start == '_')
			if(*(bios32_start + 1) == '3')
				if (*(bios32_start + 2) == '2')
					if(*(bios32_start + 3) == '_')
					{
						ret = 9;
						break;
					}
		bios32_start ++;
	}

	pci_entry_point = (unsigned long) ( *(bios32_start + 4) + (*(bios32_start + 5) << 8) + (*(bios32_start + 6) << 16));

	return ret;

}

/*
 *
 * Call the PCI Bios entry point 
 *
 * NOTE: code segment -> 0x8 selectors after base
 *
 * NOTE: Address is in the format segment:offset
 *
 * NOTE: The following code has the format described below:
 *
 * CS:0x1
 *
 *  Output:
 *      eax (a) stores ret
 *      ebx (b) stores base
 *      ecx (c) stores length
 *      edx (d) stores offset
 *      
 *  Input:
 *      PCI_BIOS_SERVICE_ID goes to eax (0'th variable in order)
 *      0 goes to ebx (1st variable in order)
 *      Address in segment:offset format, of PCI entry point, goes to edi
 *
 *  Activity:
 *      A long call is made to PCI entry point (which is the 6th variable)
 *      The 6th variable is also stored in edi
 *      Hence a long call is made to the value stored in edi
 *
 */

unsigned int det_pci_bios_in_SD()
{

	dword base, length, offset;

	base = length = offset = 0;
	unsigned char ret = 5;

	ind.segment = 0x8;
	ind.addr = (unsigned long) pci_entry_point;

	asm volatile("lcall *(%6)" : "=a" (ret), "=b" (base), "=c" (length), "=d" (offset): "0" (PCI_BIOS_SERVICE_ID), "1" (0), "D" (&ind));

        interface.segment = 0x8;
	interface.addr = base + offset;

	return (unsigned int) ret;

}

/*
 *
 * PCI bios present call
 *
 * NOTE: same assembly logic as in the comment section CS:0x1
 *
 */
unsigned int pci_bios_present()
{

	unsigned short ax, bx;
	unsigned char cl;
	unsigned long edx, flags;

	asm volatile("lcall *(%6) ; pushf ; popl %%edi" : "=a" (ax), "=b" (bx), "=c" (cl), "=d" (edx), "=D" (flags): "0" (PCI_IF_PCI_FUNC_ID | PCI_IF_BIOS_PRESENT), "D" (&interface));

	if( ((ax >> 8) == 0) && ((flags & 0x0001) == 0))
		return (unsigned int) 0;
	else
		return (unsigned int) 1;

}
	
/*
 *
 * PCI read dword function
 *
 * NOTE: 
 *  Bus number = 8 bits long (8 bits set to 1 is FF in hex, so & with FF)
 *  Device number = 5 bits long (First 5 MSB set to 1 is 1F in hex, so & with 1F)
 *  Function number = 3 bits long (First 3 MSB set to 1 is 7 in hex, so & with 7)
 *
 * Positions:
 *  NOTE: Read config = PCI_CONFIG.... statement below from right to left to understand
 *  Bit 0 to 7 PCI register number
 *  Bit 8 to 10 function number (that is why the left shift by 8 for function)
 *  Bit 11 to 15 device number (that is why the left shift by 11)
 *  Bit 16 to 23 bus number (that is why the left shift by 16)
 *  Bit 24 to 30 Reserved
 *  Bit 31 = PCI_CONFIG_ENABLE = 1
 *
 *
 */
dword pci_read_dword(dword bus, dword dev, dword func, dword reg)
{
        dword config;
        dword val = 0;

        config = PCI_CONFIG_ENABLE | ((bus & 0xFF) << 16) | ((dev & 0x1F) << 11) |
         ((func & 0x07) << 8) | (reg & 0xFC);
         
         outl(PCI_CONFIG_ADDR, config);
         
         val = inl(PCI_CONFIG_DATA);
         
         return val;
}

/*
 *
 * PCI write dword function
 *
 */
void pci_write_dword(dword bus, dword dev, dword func, dword reg, dword val)
{
        dword config;

        config = PCI_CONFIG_ENABLE | ((bus & 0xFF) << 16) | ((dev & 0x1F) << 11) |
         ((func & 0x07) << 8) | (reg & 0xFC);

         outl(PCI_CONFIG_ADDR, config);

         outl(PCI_CONFIG_DATA, val);
}

/*
 *
 * PCI read word function
 *
 */
word pci_read_word(word bus, word dev, word func, word reg)
{
        dword config;
        word val;

        config = PCI_CONFIG_ENABLE | ((bus & 0xFF) << 16) | ((dev & 0x1F) << 11) |
         ((func & 0x07) << 8) | (reg & 0xFC);
         
         outl(PCI_CONFIG_ADDR, config);
         
         val = inw(PCI_CONFIG_DATA + (reg & 3));
         
         return val;
}

/*
 *
 * PCI write word function
 *
 */
void pci_write_word(dword bus, dword dev, dword func, dword reg, word val)
{
        dword config;

        config = PCI_CONFIG_ENABLE | ((bus & 0xFF) << 16) | ((dev & 0x1F) << 11) |
         ((func & 0x07) << 8) | (reg & 0xFC);

         outl(PCI_CONFIG_ADDR, config);

         outw(PCI_CONFIG_DATA + (reg & 3), val);
}

/*
 *
 * PCI scan vendor function
 *
 * NOTE: Device ID is the first 16 bits and vendor ID is the next 16 bits of the type returned
 *
 */
word pci_scan_vendor(word bus, word dev, word func)
{
	dword type, vendorid;

        type = pci_read_word(bus, dev, func, PCI_SPACE_DEVICEID);
        vendorid = type & 0xFFFF;

        if ( vendorid != 0xFFFF)
                return vendorid;
        else
	        return 900;
}

/*
 *
 * PCI scan device function
 *
 * NOTE: Device ID is the first 16 bits and vendor ID is the next 16 bits of the type returned
 *
 */
word pci_scan_device(word bus, word dev, word func)
{
	dword type, vendorid;
	type = pci_read_dword(bus, dev, func, PCI_SPACE_DEVICEID); 
	vendorid = type & 0xFFFF;

	if(vendorid != 0xFFFF)
	        return (type >> 16 )& 0xFFFF;
	else
		return 900;
}

/*
 *
 * PCI scan ioentry function
 *
 *  This finds the address to which we must do IO, so that we can operate on
 *   the specific PCI device
 *
 */
unsigned long pci_scan_ioentry(dword bus, dword dev, dword func)
{
	unsigned long ret = 0, addr = 0;
	word reg;
		
	for (reg = PCI_BASE_ADDRESS_0; reg <= PCI_BASE_ADDRESS_5; reg +=4)
	{
		addr = pci_read_dword(bus, dev, func, reg);

		
		if ((addr & PCI_BASE_ADDRESS_IO_MASK) == 0 || (addr & PCI_BASE_ADDRESS_SPACE_IO) == 0)
						continue;
		
		addr &= PCI_BASE_ADDRESS_IO_MASK;
				
		ret = addr;
	}
return ret;
}

/*
 *
 * scan PCI buses
 *
 */
unsigned int scan_pci_buses()
{

word bus, dev;

for (bus = 0; bus < 7; bus ++)
{
	for (dev = 0; dev < 32; dev ++)
	{
		int devv, ven;

		devv = pci_scan_device(bus, dev, 0);
		ven = pci_scan_vendor(bus, dev, 0);
		if (ven != 900 && devv != 900) {
			if (ven == 0x1022 && devv == 0x2000) {
				pcnet_dev = dev;
				pcnet_bus = bus;
			}
		}
	}
}
return 10;
}

/*
 *
 * Set device to be a busmaster in case BIOS neglected to do so.
 *  Also adjust PCI latency timer to a reasonable value, 32.
 *
 */
void adjust_pci_device(dword bus, dword dev, dword fun)
{
	unsigned long new_command, pci_command;

	pci_command = pci_read_dword(bus, dev, fun, PCI_SPACE_COMMAND);
	new_command = pci_command | PCI_COMMAND_MASTER|PCI_COMMAND_IO;
	if (pci_command != new_command) {
		pci_write_dword(bus, dev, fun, PCI_SPACE_COMMAND, new_command);
	}
}

